package com.bolt.support.spring.exception.aop;

import com.bolt.common.utils.ExceptionUtil;
import com.bolt.common.utils.ValidationUtil;
import com.bolt.convention.data.ResultMessage;
import com.bolt.convention.data.Results;
import com.bolt.convention.data.code.CommonCode;
import com.bolt.convention.exception.ServiceException;
import com.bolt.convention.exception.ServiceValidException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;

import javax.validation.ConstraintViolationException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * 针对于范围值为ResultMessage类型的方法进行异常拦截
 */
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE + 18)
public class ReturnResultExceptionAspect {
    private final static Logger logger = LoggerFactory.getLogger(ReturnResultExceptionAspect.class);

    private static final String EMPTY = "";
    private static final String SPLITTER = ",";
    private static final String DOT = ".";

    private final ParameterNameDiscoverer parameterNameDiscoverer;

    @Autowired(required = false)
    private ReturnResultExceptionHandler returnResultExceptionHandler;

    public ReturnResultExceptionAspect() {
        parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    }

    public ReturnResultExceptionAspect(ParameterNameDiscoverer parameterNameDiscoverer) {
        this.parameterNameDiscoverer = parameterNameDiscoverer;
    }

    /**
     * 所有返回值为Result的子类的方法
     */
    @Pointcut("execution(com.bolt.convention.data.ResultMessage+ *..*.*(..)) && !execution(* com.bolt.support.spring.exception.aop.ReturnResultExceptionHandler.*(..))")
    private void anyResult() {
    }

    @Around("anyResult()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 如果涉及到返回Result对象的，强制不能返回空对象
        boolean isResultClass = ResultMessage.class == method.getReturnType();
        try {
            Object returnValue = joinPoint.proceed();
            if (returnValue == null) {
                throw new ServiceException(CommonCode.RETURN_NULL_ERROR);
            }
            return returnValue;
        } catch (ConstraintViolationException e) {
            List<ResultMessage.ViolationItem> violationItems = ValidationUtil
                    .convertToResultViolationItems(
                            e.getConstraintViolations());
            if (isResultClass) {
                return Results.invalid(violationItems);
            }
            return getResultValue(method, e)
                    .setCode(CommonCode.INVALID_ARGS.code())
                    .setMessage(CommonCode.INVALID_ARGS.message())
                    .setViolationItems(violationItems);
        } catch (ServiceValidException e) {
            if (logger.isInfoEnabled()) {
                logger.info("method:" + buildOperationName(method) + " " + e.toString());
            }
            if (isResultClass) {
                if (!CollectionUtils.isEmpty(e.getViolationItems())) {
                    return Results.invalid(e.getMessage(), e.getViolationItems());
                } else {
                    return Results.invalid(e.getMessage());
                }
            }
            return getResultValue(method, e)
                    .setCode(CommonCode.INVALID_ARGS.code())
                    .setMessage(CommonCode.INVALID_ARGS.message())
                    .setViolationItems(e.getViolationItems());
        } catch (ServiceException e) {
            if (logger.isInfoEnabled()) {
                logger.info("method:" + buildOperationName(method) + " " + e.toString());
            }
            if (isResultClass) {
                return Results.failure(e);
            }
            return getResultValue(method, e)
                    .setCode(e.getCode())
                    .setMessage(e.getMessage());
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
            // 先行执行自定义异常增强
            Optional<ResultMessage> handleResult = this.customExceptionHandling(e, method, joinPoint.getArgs());
            if (handleResult.isPresent()) {
                return handleResult.get();
            }
            if (isResultClass) {
                return Results.error(e);
            }
            return getResultValue(method, e)
                    .setCode(CommonCode.UNKNOWN_ERROR.code())
                    .setMessage(CommonCode.UNKNOWN_ERROR.message())
                    .setErrorClass(e.getClass().getName())
                    .setErrorStack(ExceptionUtil.stacktraceToString(e));
        }
    }

    private Optional<ResultMessage> customExceptionHandling(Throwable e, Method method, Object[] args) {
        if (Objects.isNull(returnResultExceptionHandler)) {
            return Optional.empty();
        }
        ResultMessage result = null;
        try {
            result = returnResultExceptionHandler.handleReturnResultException(e, method, args);
        } catch (Throwable e1) {
            logger.error("Return result exception advice occurrence of user exception", e1);
        }
        return Optional.ofNullable(result);
    }

    private ResultMessage getResultValue(Method method, Throwable throwable) throws Throwable {
        try {
            return (ResultMessage) method.getReturnType().newInstance();
        } catch (Exception e) {
            logger.error("method:" + buildOperationName(method) + " " + e.getMessage(), e);
            throw throwable;
        }
    }

    private String buildOperationName(Method method) {
        String args = join(parameterNameDiscoverer.getParameterNames(method), SPLITTER);
        return method.getDeclaringClass().getSimpleName() +
                DOT + method.getName() +
                "(" + args + ")";
    }

    private String join(String[] params, String splitter) {
        StringBuilder sb = new StringBuilder(EMPTY);
        if (params == null || params.length == 0) {
            return EMPTY;
        }
        for (String param : params) {
            sb.append(param).append(splitter);
        }
        return sb.substring(0, sb.length() - 1);
    }
}

 